package ants.bot;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;

import ants.Ants;
import ants.map.Coordinate;
import ants.map.Direction;
import ants.map.scent.CombatScent;
import ants.map.scent.CombatScentMap;
import ants.map.scent.Scent;
import ants.map.scent.ScentMap;
import ants.map.scent.ScentedGameMap;
import ants.map.tile.AntTile;

public class GameBot implements Bot {

	private static final double MAP_DIFFUSION_TIME_USE = 0.5;

	private static final int MAX_DIFFUSION = 50;
	private static final double SCENT_WEIGHT_ENEMY = 0.15;
	private static final double SCENT_WEIGHT_EXPLORE = 1.5;
	private static final double SCENT_WEIGHT_FOOD = 6.5;
	private static final double SCENT_WEIGHT_HILL = 2.5;

	private final Ants ants;
	private final boolean debug;
	private final ScentedGameMap gameMap;
	private final Orders orders;
	private final WarOptionComparator warOptionComparator;

	public GameBot(boolean debug) {
		this.gameMap = new ScentedGameMap();
		this.ants = new Ants(this, gameMap);
		this.orders = new Orders();
		this.warOptionComparator = new WarOptionComparator();
		this.debug = debug;
	}

	private void doPeaceMove(AntTile myAnt) {

		LinkedList<PeaceOption> options = getPeaceOptions(myAnt);
		Collections.sort(options);

		while (!options.isEmpty()) {
			PeaceOption option = options.removeLast();
			if (makeMove(myAnt, option.getTo())) {
				if (debug)
					System.err.println("peace move: " + myAnt + " to " + option.getTo()
							+ ", scent: " + option.getScent() + ", value: "
							+ option.getScentValue());
				return;
			}
		}
	}

	@Override
	public void doTurn() {

		int diffusionCount = 0;
		while (timeRemains()) {
			++diffusionCount;
			gameMap.diffuse();
			if (diffusionCount >= MAX_DIFFUSION)
				break;
		}

		System.err.println("turn " + ants.getTurn() + ", diffusion count: "
				+ diffusionCount);

		if (debug) {
			gameMap.print();
			if (ants.getTurn() >= 270)
				System.err.println("say what?");
		}

		orders.clear();

		// TODO defense force

		for (AntTile myAnt : gameMap.getMyAnts()) {
			if (hasEnemyAlertScent(myAnt)) {
				doWarMove(myAnt);
			} else
				doPeaceMove(myAnt);
		}

	}

	private void doWarMove(AntTile myAnt) {

		int threatLevelAlert = myAnt.getAlertThreat();
		int threatLevelDanger = myAnt.getDangerThreat();
		if (debug)
			System.err.println("war move: " + myAnt + ", alert threat: "
					+ threatLevelAlert + ", danger threat: " + threatLevelDanger);

		LinkedList<WarOption> options = getWarOptions(myAnt);
		warOptionComparator.setAnt(myAnt);
		// TODO set offensive or defensive stance
		Collections.sort(options, warOptionComparator);

		while (!options.isEmpty()) {
			WarOption option = options.removeLast();
			if (makeMove(myAnt, option.getTo())) {
				if (debug)
					System.err.println("war move: " + myAnt + " to " + option.getTo());
				return;
			}
		}

	}

	public ScentedGameMap getGameMap() {
		return gameMap;
	}

	private LinkedList<PeaceOption> getPeaceOptions(Coordinate c) {
		LinkedList<PeaceOption> options = new LinkedList<PeaceOption>();

		Collection<Coordinate> optionCoordinates = gameMap.getNeighbors(c);
		optionCoordinates.add(c);

		for (Coordinate destiniation : optionCoordinates) {

			int foodScent = (int) (SCENT_WEIGHT_FOOD * gameMap.getScent(destiniation,
					Scent.Food));
			int hillScent = (int) (SCENT_WEIGHT_HILL * gameMap.getScent(destiniation,
					Scent.Hill));
			int exploreScent = (int) (SCENT_WEIGHT_EXPLORE * gameMap.getScent(
					destiniation, Scent.Explore));
			int enemyScent = (int) (SCENT_WEIGHT_ENEMY * gameMap.getScent(
					destiniation, Scent.AntEnemy));

			Scent selectedScent = Scent.Food;
			int selectedScentValue = ScentMap.WATER;
			// selectedScentValue = ScentMap.getStrongestScent(foodScent, hillScent,
			// exploreScent,
			// enemyScent);

			if (0 < ScentMap.compare(foodScent, selectedScentValue)) {
				selectedScent = Scent.Food;
				selectedScentValue = foodScent;
			}

			if (0 < ScentMap.compare(hillScent, selectedScentValue)) {
				selectedScent = Scent.Hill;
				selectedScentValue = hillScent;
			}

			if (0 < ScentMap.compare(exploreScent, selectedScentValue)) {
				selectedScent = Scent.Explore;
				selectedScentValue = exploreScent;
			}

			if (0 < ScentMap.compare(enemyScent, selectedScentValue)) {
				selectedScent = Scent.AntEnemy;
				selectedScentValue = enemyScent;
			}

			options.add(new PeaceOption(destiniation, selectedScent,
					selectedScentValue));
		}

		return options;
	}

	private LinkedList<WarOption> getWarOptions(Coordinate c) {
		LinkedList<WarOption> options = new LinkedList<WarOption>();

		Collection<Coordinate> optionCoordinates = gameMap.getNeighbors(c);
		optionCoordinates.add(c);

		for (Coordinate neighbor : optionCoordinates) {

			int alertScent = gameMap.getCombatScent(neighbor, CombatScent.EnemyAlert);
			int dangerScent = gameMap.getCombatScent(neighbor,
					CombatScent.EnemyDanger);

			WarOption option = new WarOption(neighbor, alertScent, dangerScent);
			options.add(option);

		}

		return options;
	}

	private boolean hasEnemyAlertScent(AntTile myAnt) {
		int alertScent = gameMap.getCombatScent(myAnt, CombatScent.EnemyAlert);
		return 0 < ScentMap.compare(alertScent, CombatScentMap.NO_DANGER);
	}

	private boolean makeMove(Coordinate from, Coordinate to) {
		if (from == null || to == null)
			return false;

		if (from.equals(to)) {
			orders.addOrder(from, to);
			return true;
		}

		Direction direction = gameMap.getDirection(from, to);

		boolean toSpaceClear = !gameMap.isMyAnt(to) || orders.hasOrderFrom(to);
		if (toSpaceClear && !gameMap.isWater(to) && !orders.hasOrderTo(to)) {
			ants.issueOrder(from, direction);
			orders.addOrder(from, to);
			return true;

		} else
			return false;
	}

	public void start(InputStream in) {
		try {
			ants.readSystemInput(in);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private boolean timeRemains() {
		if (debug)
			return true;
		long now = System.currentTimeMillis();
		long timeUsed = now - ants.getTurnStarttime();
		int turnTime = ants.getTurntime();
		long maxUseTime = (long) (MAP_DIFFUSION_TIME_USE * turnTime);

		// dev output
		// System.err.println(String.format(
		// "turn time: %s, time used: %s, max time use: %s", turnTime, timeUsed,
		// maxUseTime));

		return timeUsed < maxUseTime;
	}

}
